// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/clipboard/clipboard_aura.h"

#include <stdint.h>

#include <limits>
#include <list>

#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/clipboard/custom_data_helper.h"
#include "ui/gfx/geometry/size.h"

namespace ui {

namespace {
    const char kMimeTypeFilename[] = "chromium/filename";
    const char kMimeTypeBitmap[] = "image/bmp";
    const char kMimeTypePepperCustomData[] = "chromium/x-pepper-custom-data";
    const char kMimeTypeWebkitSmartPaste[] = "chromium/x-webkit-paste";
    const size_t kMaxClipboardSize = 1;

    // Clipboard data format used by AuraClipboard.
    enum AuraClipboardFormat {
        TEXT = 1 << 0,
        HTML = 1 << 1,
        RTF = 1 << 2,
        BOOKMARK = 1 << 3,
        BITMAP = 1 << 4,
        CUSTOM = 1 << 5,
        WEB = 1 << 6,
    };

    // ClipboardData contains data copied to the Clipboard for a variety of formats.
    // It mostly just provides APIs to cleanly access and manipulate this data.
    class ClipboardData {
    public:
        ClipboardData()
            : web_smart_paste_(false)
            , format_(0)
        {
        }

        virtual ~ClipboardData() { }

        // Bitmask of AuraClipboardFormat types.
        int format() const { return format_; }

        const std::string& text() const { return text_; }
        void set_text(const std::string& text)
        {
            text_ = text;
            format_ |= TEXT;
        }

        const std::string& markup_data() const { return markup_data_; }
        void set_markup_data(const std::string& markup_data)
        {
            markup_data_ = markup_data;
            format_ |= HTML;
        }

        const std::string& rtf_data() const { return rtf_data_; }
        void SetRTFData(const std::string& rtf_data)
        {
            rtf_data_ = rtf_data;
            format_ |= RTF;
        }

        const std::string& url() const { return url_; }
        void set_url(const std::string& url)
        {
            url_ = url;
            format_ |= HTML;
        }

        const std::string& bookmark_title() const { return bookmark_title_; }
        void set_bookmark_title(const std::string& bookmark_title)
        {
            bookmark_title_ = bookmark_title;
            format_ |= BOOKMARK;
        }

        const std::string& bookmark_url() const { return bookmark_url_; }
        void set_bookmark_url(const std::string& bookmark_url)
        {
            bookmark_url_ = bookmark_url;
            format_ |= BOOKMARK;
        }

        const SkBitmap& bitmap() const { return bitmap_; }
        void SetBitmapData(const SkBitmap& bitmap)
        {
            bitmap.copyTo(&bitmap_);
            format_ |= BITMAP;
        }

        const std::string& custom_data_format() const { return custom_data_format_; }
        const std::string& custom_data_data() const { return custom_data_data_; }
        void SetCustomData(const std::string& data_format,
            const std::string& data_data)
        {
            if (data_data.size() == 0) {
                custom_data_data_.clear();
                custom_data_format_.clear();
                return;
            }
            custom_data_data_ = data_data;
            custom_data_format_ = data_format;
            format_ |= CUSTOM;
        }

        bool web_smart_paste() const { return web_smart_paste_; }
        void set_web_smart_paste(bool web_smart_paste)
        {
            web_smart_paste_ = web_smart_paste;
            format_ |= WEB;
        }

    private:
        // Plain text in UTF8 format.
        std::string text_;

        // HTML markup data in UTF8 format.
        std::string markup_data_;
        std::string url_;

        // RTF data.
        std::string rtf_data_;

        // Bookmark title in UTF8 format.
        std::string bookmark_title_;
        std::string bookmark_url_;

        // Filenames.
        std::vector<std::string> files_;

        // Bitmap images.
        SkBitmap bitmap_;

        // Data with custom format.
        std::string custom_data_format_;
        std::string custom_data_data_;

        // WebKit smart paste data.
        bool web_smart_paste_;

        int format_;

        DISALLOW_COPY_AND_ASSIGN(ClipboardData);
    };

    // Platform clipboard implementation for Aura. This handles things like format
    // conversion, versioning of clipboard items etc. The goal is to roughly provide
    // a substitute to platform clipboards on other platforms such as GtkClipboard
    // on gtk or winapi clipboard on win.
    class AuraClipboard {
    public:
        AuraClipboard()
            : sequence_number_(0)
        {
        }

        ~AuraClipboard()
        {
            Clear();
        }

        void Clear()
        {
            sequence_number_++;
            STLDeleteContainerPointers(data_list_.begin(), data_list_.end());
            data_list_.clear();
        }

        uint64_t sequence_number() const
        {
            return sequence_number_;
        }

        // Returns the data currently on the top of the clipboard stack, NULL if the
        // clipboard stack is empty.
        const ClipboardData* GetData() const
        {
            if (data_list_.empty())
                return NULL;
            return data_list_.front();
        }

        // Returns true if the data on top of the clipboard stack has format |format|
        // or another format that can be converted to |format|.
        bool IsFormatAvailable(AuraClipboardFormat format) const
        {
            switch (format) {
            case TEXT:
                return HasFormat(TEXT) || HasFormat(BOOKMARK);
            default:
                return HasFormat(format);
            }
        }

        // Reads text from the data at the top of clipboard stack.
        void ReadText(base::string16* result) const
        {
            std::string utf8_result;
            ReadAsciiText(&utf8_result);
            *result = base::UTF8ToUTF16(utf8_result);
        }

        // Reads ascii text from the data at the top of clipboard stack.
        void ReadAsciiText(std::string* result) const
        {
            result->clear();
            const ClipboardData* data = GetData();
            if (!data)
                return;
            if (HasFormat(TEXT))
                *result = data->text();
            else if (HasFormat(HTML))
                *result = data->markup_data();
            else if (HasFormat(BOOKMARK))
                *result = data->bookmark_url();
        }

        // Reads HTML from the data at the top of clipboard stack.
        void ReadHTML(base::string16* markup,
            std::string* src_url,
            uint32_t* fragment_start,
            uint32_t* fragment_end) const
        {
            markup->clear();
            if (src_url)
                src_url->clear();
            *fragment_start = 0;
            *fragment_end = 0;

            if (!HasFormat(HTML))
                return;

            const ClipboardData* data = GetData();
            *markup = base::UTF8ToUTF16(data->markup_data());
            *src_url = data->url();

            *fragment_start = 0;
            DCHECK_LE(markup->length(), std::numeric_limits<uint32_t>::max());
            *fragment_end = static_cast<uint32_t>(markup->length());
        }

        // Reads RTF from the data at the top of clipboard stack.
        void ReadRTF(std::string* result) const
        {
            result->clear();
            const ClipboardData* data = GetData();
            if (!HasFormat(RTF))
                return;

            *result = data->rtf_data();
        }

        // Reads image from the data at the top of clipboard stack.
        SkBitmap ReadImage() const
        {
            SkBitmap img;
            if (!HasFormat(BITMAP))
                return img;

            // A shallow copy should be fine here, but just to be safe...
            const SkBitmap& clipboard_bitmap = GetData()->bitmap();
            clipboard_bitmap.copyTo(&img);
            return img;
        }

        // Reads data of type |type| from the data at the top of clipboard stack.
        void ReadCustomData(const base::string16& type,
            base::string16* result) const
        {
            result->clear();
            const ClipboardData* data = GetData();
            if (!HasFormat(CUSTOM))
                return;

            ui::ReadCustomDataForType(data->custom_data_data().c_str(),
                data->custom_data_data().size(),
                type, result);
        }

        // Reads bookmark from the data at the top of clipboard stack.
        void ReadBookmark(base::string16* title, std::string* url) const
        {
            title->clear();
            url->clear();
            if (!HasFormat(BOOKMARK))
                return;

            const ClipboardData* data = GetData();
            *title = base::UTF8ToUTF16(data->bookmark_title());
            *url = data->bookmark_url();
        }

        void ReadData(const std::string& type, std::string* result) const
        {
            result->clear();
            const ClipboardData* data = GetData();
            if (!HasFormat(CUSTOM) || type != data->custom_data_format())
                return;

            *result = data->custom_data_data();
        }

        // Writes |data| to the top of the clipboard stack.
        void WriteData(ClipboardData* data)
        {
            DCHECK(data);
            AddToListEnsuringSize(data);
        }

    private:
        // True if the data on top of the clipboard stack has format |format|.
        bool HasFormat(AuraClipboardFormat format) const
        {
            const ClipboardData* data = GetData();
            if (!data)
                return false;

            return data->format() & format;
        }

        void AddToListEnsuringSize(ClipboardData* data)
        {
            DCHECK(data);
            sequence_number_++;
            data_list_.push_front(data);

            // If the size of list becomes more than the maximum allowed, we delete the
            // last element.
            if (data_list_.size() > kMaxClipboardSize) {
                ClipboardData* last = data_list_.back();
                data_list_.pop_back();
                delete last;
            }
        }

        // Stack containing various versions of ClipboardData.
        std::list<ClipboardData*> data_list_;

        // Sequence number uniquely identifying clipboard state.
        uint64_t sequence_number_;

        DISALLOW_COPY_AND_ASSIGN(AuraClipboard);
    };

    AuraClipboard* aura_clipboard = NULL;

    AuraClipboard* GetClipboard()
    {
        if (!aura_clipboard)
            aura_clipboard = new AuraClipboard();
        return aura_clipboard;
    }

    void DeleteClipboard()
    {
        if (aura_clipboard)
            delete aura_clipboard;
        aura_clipboard = NULL;
    }

    // Helper class to build a ClipboardData object and write it to clipboard.
    class ClipboardDataBuilder {
    public:
        static void CommitToClipboard()
        {
            GetClipboard()->WriteData(GetCurrentData());
            current_data_ = NULL;
        }

        static void WriteText(const char* text_data, size_t text_len)
        {
            ClipboardData* data = GetCurrentData();
            data->set_text(std::string(text_data, text_len));
        }

        static void WriteHTML(const char* markup_data,
            size_t markup_len,
            const char* url_data,
            size_t url_len)
        {
            ClipboardData* data = GetCurrentData();
            data->set_markup_data(std::string(markup_data, markup_len));
            data->set_url(std::string(url_data, url_len));
        }

        static void WriteRTF(const char* rtf_data, size_t rtf_len)
        {
            ClipboardData* data = GetCurrentData();
            data->SetRTFData(std::string(rtf_data, rtf_len));
        }

        static void WriteBookmark(const char* title_data,
            size_t title_len,
            const char* url_data,
            size_t url_len)
        {
            ClipboardData* data = GetCurrentData();
            data->set_bookmark_title(std::string(title_data, title_len));
            data->set_bookmark_url(std::string(url_data, url_len));
        }

        static void WriteWebSmartPaste()
        {
            ClipboardData* data = GetCurrentData();
            data->set_web_smart_paste(true);
        }

        static void WriteBitmap(const SkBitmap& bitmap)
        {
            ClipboardData* data = GetCurrentData();
            data->SetBitmapData(bitmap);
        }

        static void WriteData(const std::string& format,
            const char* data_data,
            size_t data_len)
        {
            ClipboardData* data = GetCurrentData();
            data->SetCustomData(format, std::string(data_data, data_len));
        }

    private:
        static ClipboardData* GetCurrentData()
        {
            if (!current_data_)
                current_data_ = new ClipboardData;
            return current_data_;
        }

        static ClipboardData* current_data_;
    };

    ClipboardData* ClipboardDataBuilder::current_data_ = NULL;

} // namespace

// Clipboard::FormatType implementation.
Clipboard::FormatType::FormatType()
{
}

Clipboard::FormatType::FormatType(const std::string& native_format)
    : data_(native_format)
{
}

Clipboard::FormatType::~FormatType()
{
}

std::string Clipboard::FormatType::Serialize() const
{
    return data_;
}

// static
Clipboard::FormatType Clipboard::FormatType::Deserialize(
    const std::string& serialization)
{
    return FormatType(serialization);
}

bool Clipboard::FormatType::operator<(const FormatType& other) const
{
    return data_ < other.data_;
}

bool Clipboard::FormatType::Equals(const FormatType& other) const
{
    return data_ == other.data_;
}

// Various predefined FormatTypes.
// static
Clipboard::FormatType Clipboard::GetFormatType(
    const std::string& format_string)
{
    return FormatType::Deserialize(format_string);
}

// static
const Clipboard::FormatType& Clipboard::GetUrlFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeURIList));
    return type;
}

// static
const Clipboard::FormatType& Clipboard::GetUrlWFormatType()
{
    return GetUrlFormatType();
}

// static
const Clipboard::FormatType& Clipboard::GetPlainTextFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeText));
    return type;
}

// static
const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType()
{
    return GetPlainTextFormatType();
}

// static
const Clipboard::FormatType& Clipboard::GetFilenameFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeFilename));
    return type;
}

// static
const Clipboard::FormatType& Clipboard::GetFilenameWFormatType()
{
    return Clipboard::GetFilenameFormatType();
}

// static
const Clipboard::FormatType& Clipboard::GetHtmlFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeHTML));
    return type;
}

// static
const Clipboard::FormatType& Clipboard::GetRtfFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeRTF));
    return type;
}

// static
const Clipboard::FormatType& Clipboard::GetBitmapFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeBitmap));
    return type;
}

// static
const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebkitSmartPaste));
    return type;
}

// static
const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData));
    return type;
}

// static
const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType()
{
    CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypePepperCustomData));
    return type;
}

// Clipboard factory method.
Clipboard* Clipboard::Create()
{
    return new ClipboardAura;
}

// ClipboardAura implementation.
ClipboardAura::ClipboardAura()
{
    DCHECK(CalledOnValidThread());
    // Make sure clipboard is created.
    GetClipboard();
}

ClipboardAura::~ClipboardAura()
{
    DCHECK(CalledOnValidThread());
    DeleteClipboard();
}

uint64_t ClipboardAura::GetSequenceNumber(ClipboardType type) const
{
    DCHECK(CalledOnValidThread());
    return GetClipboard()->sequence_number();
}

bool ClipboardAura::IsFormatAvailable(const FormatType& format,
    ClipboardType type) const
{
    DCHECK(CalledOnValidThread());
    DCHECK(IsSupportedClipboardType(type));
    AuraClipboard* clipboard = GetClipboard();
    if (GetPlainTextFormatType().Equals(format) || GetUrlFormatType().Equals(format))
        return clipboard->IsFormatAvailable(TEXT);
    else if (GetHtmlFormatType().Equals(format))
        return clipboard->IsFormatAvailable(HTML);
    else if (GetRtfFormatType().Equals(format))
        return clipboard->IsFormatAvailable(RTF);
    else if (GetBitmapFormatType().Equals(format))
        return clipboard->IsFormatAvailable(BITMAP);
    else if (GetWebKitSmartPasteFormatType().Equals(format))
        return clipboard->IsFormatAvailable(WEB);
    else {
        const ClipboardData* data = clipboard->GetData();
        if (data && data->custom_data_format() == format.ToString())
            return true;
    }
    return false;
}

void ClipboardAura::Clear(ClipboardType type)
{
    DCHECK(CalledOnValidThread());
    DCHECK(IsSupportedClipboardType(type));
    AuraClipboard* clipboard = GetClipboard();
    clipboard->Clear();
}

void ClipboardAura::ReadAvailableTypes(ClipboardType type,
    std::vector<base::string16>* types,
    bool* contains_filenames) const
{
    DCHECK(CalledOnValidThread());
    if (!types || !contains_filenames) {
        NOTREACHED();
        return;
    }

    types->clear();
    *contains_filenames = false;
    if (IsFormatAvailable(GetPlainTextFormatType(), type))
        types->push_back(base::UTF8ToUTF16(GetPlainTextFormatType().ToString()));
    if (IsFormatAvailable(GetHtmlFormatType(), type))
        types->push_back(base::UTF8ToUTF16(GetHtmlFormatType().ToString()));
    if (IsFormatAvailable(GetRtfFormatType(), type))
        types->push_back(base::UTF8ToUTF16(GetRtfFormatType().ToString()));
    if (IsFormatAvailable(GetBitmapFormatType(), type))
        types->push_back(base::UTF8ToUTF16(kMimeTypePNG));

    AuraClipboard* clipboard = GetClipboard();
    if (clipboard->IsFormatAvailable(CUSTOM) && clipboard->GetData()) {
        ui::ReadCustomDataTypes(clipboard->GetData()->custom_data_data().c_str(),
            clipboard->GetData()->custom_data_data().size(), types);
    }
}

void ClipboardAura::ReadText(ClipboardType type, base::string16* result) const
{
    DCHECK(CalledOnValidThread());
    GetClipboard()->ReadText(result);
}

void ClipboardAura::ReadAsciiText(ClipboardType type,
    std::string* result) const
{
    DCHECK(CalledOnValidThread());
    GetClipboard()->ReadAsciiText(result);
}

void ClipboardAura::ReadHTML(ClipboardType type,
    base::string16* markup,
    std::string* src_url,
    uint32_t* fragment_start,
    uint32_t* fragment_end) const
{
    DCHECK(CalledOnValidThread());
    GetClipboard()->ReadHTML(markup, src_url, fragment_start, fragment_end);
}

void ClipboardAura::ReadRTF(ClipboardType type, std::string* result) const
{
    DCHECK(CalledOnValidThread());
    GetClipboard()->ReadRTF(result);
}

SkBitmap ClipboardAura::ReadImage(ClipboardType type) const
{
    DCHECK(CalledOnValidThread());
    return GetClipboard()->ReadImage();
}

void ClipboardAura::ReadCustomData(ClipboardType clipboard_type,
    const base::string16& type,
    base::string16* result) const
{
    DCHECK(CalledOnValidThread());
    GetClipboard()->ReadCustomData(type, result);
}

void ClipboardAura::ReadBookmark(base::string16* title,
    std::string* url) const
{
    DCHECK(CalledOnValidThread());
    GetClipboard()->ReadBookmark(title, url);
}

void ClipboardAura::ReadData(const FormatType& format,
    std::string* result) const
{
    DCHECK(CalledOnValidThread());
    GetClipboard()->ReadData(format.ToString(), result);
}

void ClipboardAura::WriteObjects(ClipboardType type, const ObjectMap& objects)
{
    DCHECK(CalledOnValidThread());
    DCHECK(IsSupportedClipboardType(type));
    for (ObjectMap::const_iterator iter = objects.begin(); iter != objects.end();
         ++iter) {
        DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
    }
    ClipboardDataBuilder::CommitToClipboard();
}

void ClipboardAura::WriteText(const char* text_data, size_t text_len)
{
    ClipboardDataBuilder::WriteText(text_data, text_len);
}

void ClipboardAura::WriteHTML(const char* markup_data,
    size_t markup_len,
    const char* url_data,
    size_t url_len)
{
    ClipboardDataBuilder::WriteHTML(markup_data, markup_len, url_data, url_len);
}

void ClipboardAura::WriteRTF(const char* rtf_data, size_t data_len)
{
    ClipboardDataBuilder::WriteRTF(rtf_data, data_len);
}

void ClipboardAura::WriteBookmark(const char* title_data,
    size_t title_len,
    const char* url_data,
    size_t url_len)
{
    ClipboardDataBuilder::WriteBookmark(title_data, title_len, url_data, url_len);
}

void ClipboardAura::WriteWebSmartPaste()
{
    ClipboardDataBuilder::WriteWebSmartPaste();
}

void ClipboardAura::WriteBitmap(const SkBitmap& bitmap)
{
    ClipboardDataBuilder::WriteBitmap(bitmap);
}

void ClipboardAura::WriteData(const FormatType& format,
    const char* data_data,
    size_t data_len)
{
    ClipboardDataBuilder::WriteData(format.ToString(), data_data, data_len);
}

} // namespace ui
